package com.emeraldparser.collect;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import com.google.common.base.Preconditions;
import com.google.common.collect.ForwardingMultiset;
import com.google.common.collect.Lists;
import com.google.common.collect.Multiset;

public class ListenableMultiset<E> extends ForwardingMultiset<E> {

	private final Multiset<E> delegate;
	private final List<MultisetListener<? super E>> listeners = Lists.newArrayListWithCapacity(1);

	public ListenableMultiset(Multiset<E> delegate) {
		Preconditions.checkNotNull(delegate, "Delegate must not be null");
		this.delegate = delegate;
	}

	public static <E> ListenableMultiset<E> create(Multiset<E> delegate) {
		return new ListenableMultiset<E>(delegate);
	}

	public ListenableMultiset<E> withListener(MultisetListener<? super E> listener) {
		listeners.add(listener);
		return this;
	}

	@Override
	protected Multiset<E> delegate() {
		return delegate;
	}

	private void elementsAdded(E element, int occurrences) {
		elementsAdded(element, occurrences, -1);
	}

	private void elementsAdded(E element, int occurrences, int oldCount) {
		for (MultisetListener<? super E> listener : listeners) {
			listener.elementsAdded(element, occurrences);
		}
	}

	private void elementsRemoved(E element, int occurrences) {
		elementsRemoved(element, occurrences, -1);
	}

	private void elementsRemoved(E element, int occurrences, int oldCount) {
		for (MultisetListener<? super E> listener : listeners) {
			listener.elementsRemoved(element, occurrences);
		}
	}

	@Override
	public int add(E element, int occurrences) {
		int oldCount = super.add(element, occurrences);
		elementsAdded(element, occurrences, oldCount);
		return oldCount;
	}

	@Override
	public int remove(Object element, int occurrences) {
		// TODO Auto-generated method stub
		int oldCount = super.remove(element, occurrences);
		if (oldCount > 0) {
			@SuppressWarnings("unchecked")
			E castElement = (E) element; // It existed in our collection, so it must be the right type
			
			elementsRemoved(castElement, occurrences, oldCount);
		}
		return oldCount;
	}

	@Override
	public Set<E> elementSet() {
		// TODO Auto-generated method stub
		return super.elementSet();
	}

	@Override
	public Set<Multiset.Entry<E>> entrySet() {
		// TODO Auto-generated method stub
		return super.entrySet();
	}

	@Override
	public int setCount(E element, int count) {
		// TODO Auto-generated method stub
		return super.setCount(element, count);
	}

	@Override
	public boolean setCount(E element, int oldCount, int newCount) {
		// TODO Auto-generated method stub
		return super.setCount(element, oldCount, newCount);
	}

	@Override
	public Iterator<E> iterator() {
		// TODO Auto-generated method stub
		return super.iterator();
	}

	@Override
	public boolean removeAll(Collection<?> collection) {
		// TODO Auto-generated method stub
		return super.removeAll(collection);
	}

	@Override
	public boolean add(E element) {
		boolean modified = super.add(element);
		if (modified) {
			elementsAdded(element, 1);
		}
		return modified;
	}

	@Override
	public boolean remove(Object object) {
		boolean modified = super.remove(object);
		if (modified) {
			@SuppressWarnings("unchecked")
			E element = (E) object; // The delegate allowed it into the collection, so it must be the right type
			
			elementsRemoved(element, 1);
		}
		return modified;
	}

	@Override
	public boolean addAll(Collection<? extends E> collection) {
		// TODO Auto-generated method stub
		return super.addAll(collection);
	}

	@Override
	public boolean retainAll(Collection<?> collection) {
		// TODO Auto-generated method stub
		return super.retainAll(collection);
	}

	@Override
	public void clear() {
		// TODO Auto-generated method stub
		super.clear();
	}

}
